/**
 * 
 */
package com.csc.bankingsystem.web.spring.security;

import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;

import com.csc.bankingsystem.core.entity.HistoryRecord;
import com.csc.bankingsystem.core.entity.TokenRecord;
import com.csc.bankingsystem.core.entity.UserInfo;
import com.csc.bankingsystem.core.entity.UserInfoRole;
import com.csc.bankingsystem.core.service.HistoryRecordService;
import com.csc.bankingsystem.core.service.TokenRecordService;
import com.csc.bankingsystem.core.service.UserInfoService;
import com.csc.bankingsystem.core.util.ConstantManager;
import com.csc.bankingsystem.core.util.EncryptionUtil;
import com.csc.bankingsystem.web.exception.BSBadCredentialsException;
import com.csc.bankingsystem.web.util.CaptchaServiceSingleton;

/**
 * @author angelkid
 *
 */
public class BSAuthenticationProvider implements AuthenticationProvider {

	@Autowired
	CustomUserDetailsService customUserDetailsService;

	@Autowired
	private UserInfoService userInfoService;

	@Autowired
	private TokenRecordService tokenRecordService;

	@Autowired
	private CaptchaCaptureFilter captchaCaptureFilter;

	@Autowired
	private HistoryRecordService historyRecordService;
	
	@Override
	public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		String username = String.valueOf(authentication.getPrincipal());
		String password = String.valueOf(authentication.getCredentials());
		String tokenStr = captchaCaptureFilter.getRequest().getParameter(
				ConstantManager.TOKEN_PARAM);
		if (!StringUtils.isEmpty(tokenStr)) {
			return processTokenRequest(tokenStr);
		} else {
			return processNoneTokenRequest(username, password);
		}
	}

	private Authentication processTokenRequest(String tokenStr) {
		// Have Token String
		try {
			String[] arrStr = tokenStr
					.split(ConstantManager.TOKEN_PARAM_SEPARATOR);
			String loginIdParam = arrStr[1];
			String tokenMD5 = EncryptionUtil
					.generateMD5HashFromString(arrStr[0] + arrStr[1]);
			Date now = new Date();

			UserInfo userInfo = userInfoService
					.getUserInfoByLoginId(loginIdParam);
			if (userInfo == null) {
				throw new BSBadCredentialsException(
						ConstantManager.TOKEN_NOT_FOUND, "Token not found");
			}

			TokenRecord tokenRecord = tokenRecordService
					.findFirstByTokenAndUserInfoId(tokenMD5, userInfo.getId());
			if (tokenRecord == null) {
				throw new BSBadCredentialsException(
						ConstantManager.TOKEN_NOT_FOUND, "Token not found");
			} else if (tokenRecord.getExpireDate() != null
					&& (now.compareTo(tokenRecord.getExpireDate()) > 0)) {
				throw new BSBadCredentialsException(
						ConstantManager.TOKEN_EXPIRED, "Token expired");
			} else {
				@SuppressWarnings("unchecked")
				List<GrantedAuthority> authorityList = (List<GrantedAuthority>) customUserDetailsService
						.getAuthorities(userInfo.getUserInfoRoles());
				BSUsernamePasswordAuthenticationToken upToken = new BSUsernamePasswordAuthenticationToken(
						userInfo.getLoginId(), userInfo.getPassword(),
						authorityList, arrStr[0]);
				return upToken;
			}
		} catch (NoSuchAlgorithmException e) {
			throw new BSBadCredentialsException(
					ConstantManager.ALGORITHM_NOT_SUPPORT,
					"Server is not support Algorithm");
		} catch (Exception e) {
			throw new BSBadCredentialsException(ConstantManager.INVALID_TOKEN,
					"Token is invalid");
		}
	}

	private Authentication processNoneTokenRequest(String username,
			String password) {
		if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
			throw new BSBadCredentialsException(
					ConstantManager.NO_USERNAME_OR_PASSWORD,
					"No Username and/or Password Provided.");
		} else if (StringUtils.isBlank(captchaCaptureFilter
				.getUserCaptchaResponse())) {
			throw new BSBadCredentialsException(ConstantManager.NO_CAPTCHA,
					"Captcha Response is Empty");
		} else {
			// Send HTTP request to validate user's Captcha
			Date now = new Date();
			String captchaId = captchaCaptureFilter.getRequest().getSession()
					.getId();
			// .getId();
			boolean captchaPassed = CaptchaServiceSingleton.getInstance()
					.validateResponseForID(captchaId,
							captchaCaptureFilter.getUserCaptchaResponse());
			// boolean captchaPassed = true;

			// Check if valid
			if (captchaPassed) {
				resetCaptchaFields();
				UserInfo user = userInfoService.getUserInfoByLoginId(username);
				if (user == null) {
					throw new BSBadCredentialsException(
							ConstantManager.INVALID_USERNAME_OR_PASSWORD,
							"Invalid Username and/or Password.");
				}
				// Get duration in milisecond
				if (!user.isEnabled()) {
					long duration = now.getTime()
							- user.getLastVisit().getTime();
					if (duration > ConstantManager.LOCK_USER_TIME_MILISECOND) {
						user.setEnabled(true);
						user.setNumAttempt(0);
					} else {
						throw new BSBadCredentialsException(
								ConstantManager.USER_IS_LOCKED,
								"User is locked");
					}
				}

				user.setLastVisit(now);
				int numAttempt = user.getNumAttempt() + 1;
				try {
					password = EncryptionUtil
							.generateMD5HashFromString(password
									+ user.getSalt());
				} catch (NoSuchAlgorithmException e) {
					throw new BSBadCredentialsException(
							ConstantManager.ALGORITHM_NOT_SUPPORT,
							"Server is not support Algorithm");
				}
				if (user.getPassword().equals(password)) {
					String token;
					try {
						user.setNumAttempt(0);
						historyRecordService.saveLoginSuccessByUserInfoObject(user);
						// create token for further request
						token = tokenRecordService
								.createTokenForLoginId(username);

						List<UserInfoRole> userInfoRoles = userInfoService
								.getUserInfoRoleByLoginId(username);
						@SuppressWarnings("unchecked")
						List<GrantedAuthority> authorityList = (List<GrantedAuthority>) customUserDetailsService
								.getAuthorities(userInfoRoles);
						BSUsernamePasswordAuthenticationToken upToken = new BSUsernamePasswordAuthenticationToken(
								username, password, authorityList, token);
						return upToken;
					} catch (NoSuchAlgorithmException e) {
						throw new BSBadCredentialsException(
								ConstantManager.ALGORITHM_NOT_SUPPORT,
								"Server is not support Algorithm");
					}
				} else {

					user.setNumAttempt(numAttempt);
					if (numAttempt >= ConstantManager.MAX_NUM_ATTEMPT) {
						user.setEnabled(false);
					}
					userInfoService.updateByUserObject(user);

					throw new BSBadCredentialsException(
							ConstantManager.INVALID_USERNAME_OR_PASSWORD,
							"Invalid Username and/or Password.");
				}
			} else {
				resetCaptchaFields();
				throw new BSBadCredentialsException(
						ConstantManager.CAPTCHA_NOT_MATCH, "Captcha not match.");
			}
		}
	}

	@Override
	public boolean supports(Class<?> authentication) {
		return (UsernamePasswordAuthenticationToken.class
				.isAssignableFrom(authentication));
	}

	/**
	 * Reset Captcha fields
	 */
	public void resetCaptchaFields() {
		captchaCaptureFilter.setUserCaptchaResponse(null);
	}

	public CaptchaCaptureFilter getCaptchaCaptureFilter() {
		return captchaCaptureFilter;
	}

	public void setCaptchaCaptureFilter(
			CaptchaCaptureFilter captchaCaptureFilter) {
		this.captchaCaptureFilter = captchaCaptureFilter;
	}
}
